package com.lksoft.bz;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.geom.AffineTransform;

import javax.swing.JPanel;

public class StitchPanel extends JPanel implements MouseWheelListener {
	private static final long serialVersionUID = 7018498044379323034L;
	
	/** Letters font */
	private static final Font letterFont = new Font("Monospaced", Font.PLAIN, 5);
	
	/** Beads size at no zoom */
	private static final int BEAD_WIDTH = 7;
	private static final int BEAD_HEIGHT = 9;
	
	// Luminosity map (per line)
	final static int[] addColor = new int[] { -40, -30, 30, 50, 40, 15, 0, -40, -55 }; 
	
	
	/** Background color */
	private static final Color BACKGROUND = Color.WHITE;
	/** Frame color */
	private static final Color FRAME_COLOR = new Color(60, 140, 200);

	/** Stich coordinates */
	static final int STITCH_X = 20;
	static final int STITCH_Y = 20;

	/** Default error for calculating size */
	private static final int SIZE_EPS = 20;
	
	/** Parent component */
	Bz parent;
	
	/** Current stitch pattern */
	private StitchPattern stitchPattern = new StitchPattern();
	
	/** Zoom */
	float zoom = 1.0f;
	
	/** Draw beads frame */
	boolean showFrame = true;

	/**
	 * Constructor
	 */
	public StitchPanel(Bz parent){
		this.parent = parent;
		recalculatePanelSize();
		
		addMouseWheelListener(this);
	}
	
	@Override
	public void paint(Graphics g){	
		super.paint(g);

		AffineTransform old = ((Graphics2D)g).getTransform();
		((Graphics2D)g).scale(zoom, zoom);

		
		// Paint background
		g.setColor(BACKGROUND);
		g.fillRect(0, 0, getWidth(), getHeight());
		
		// Paint stitch pattern
		paintStitchPattern(g);
		
		((Graphics2D)g).setTransform(old);
	}

	/**
	 * Paint the whole stitch pattern
	 * @param g
	 */
	private void paintStitchPattern(Graphics g) {
		int drawX;
		int drawY;
		
		for( int col=0; col<stitchPattern.getWidth(); col++ ){
			drawX = STITCH_X + col*BEAD_WIDTH;
			drawY = STITCH_Y;
			
			// Payote offset
			if( stitchPattern.getStitchType() == StitchType.PEYOTE && (col%2==1)){
				drawY += BEAD_HEIGHT/2;
			}
			
			// First draw frames
			for( int row=0; row<stitchPattern.getHeight(); row++ ){
				// Draw Frame
				g.setColor(FRAME_COLOR);
				g.fillRect(drawX, drawY, BEAD_WIDTH, 1);
				g.fillRect(drawX, drawY+BEAD_HEIGHT-1, BEAD_WIDTH, 1);
				g.fillRect(drawX, drawY, 1, BEAD_HEIGHT);
				g.fillRect(drawX+BEAD_WIDTH-1, drawY, 1, BEAD_HEIGHT);

				drawY += BEAD_HEIGHT;
			}
		}
		
		for( int col=0; col<stitchPattern.getWidth(); col++ ){
			drawX = STITCH_X + col*BEAD_WIDTH;
			drawY = STITCH_Y;
			
			// Payote offset
			if( stitchPattern.getStitchType() == StitchType.PEYOTE && (col%2==1)){
				drawY += BEAD_HEIGHT/2;
			}
			
			// Then draw beads
			for( int row=0; row<stitchPattern.getHeight(); row++ ){
				
				int color = stitchPattern.getBead(col, row);
				if( color != 0 ){
					drawBead(g, drawX, drawY, color, showFrame);
					drawLetter(g, drawX, drawY, stitchPattern.getUsedBeads().get(color));
				}
				drawY += BEAD_HEIGHT;
			}
		}
	}

	/**
	 * Draw a single bead
	 * @param g
	 * @param drawX
	 * @param drawY
	 * @param color
	 * @param frame
	 */
	public static void drawBead(Graphics g, int drawX, int drawY, int color, boolean frame) {
		int r = (color & 0xFF0000) >> 16;
		int gr = (color & 0xFF00) >> 8;
		int b = (color & 0xFF);
		
		for( int i=0; i<BEAD_HEIGHT; i++ ){
			int rowR = clamp(r + addColor[i], 0, 255);
			int rowG = clamp(gr + addColor[i], 0, 255);
			int rowB = clamp(b + addColor[i], 0, 255);
			
			int rowColor = rowR<<16 | rowG<<8 | rowB;
			
			// Draw bead
			g.setColor(ColorMap.getColor(rowColor));
			g.fillRect(drawX, drawY+i, BEAD_WIDTH, 1);
		}
		
		// Draw Frame
		if( frame ){
			int max = r;
			max = Math.max(max, gr);
			max = Math.max(max, b);
			
			Color frameColor = max < 40 ? Color.WHITE : Color.BLACK;
			g.setColor(frameColor);
			g.fillRect(drawX, drawY, BEAD_WIDTH, 1);
			g.fillRect(drawX, drawY+BEAD_HEIGHT-1, BEAD_WIDTH, 1);
			g.fillRect(drawX+BEAD_WIDTH-1, drawY, 1, BEAD_HEIGHT);
			g.fillRect(drawX, drawY, 1, BEAD_HEIGHT);
		}
	}
	
	/**
	 * Draw the beadUsage letter over a bead
	 * @param g
	 * @param drawX
	 * @param drawY
	 * @param beadUsage
	 */
	private void drawLetter(Graphics g, int drawX, int drawY, BeadUsage beadUsage) {
		if( beadUsage.getLetter() == '\0' ) return;
		
		int color = beadUsage.getBead().getColor();
		int max = (color >> 16) & 0xFF;
		max = Math.max(max, (color >> 8) & 0xFF);
		max = Math.max(max, color & 0xFF);
		
		Color textColor = max > 180 ? Color.BLACK : Color.WHITE;
		g.setColor(textColor);
		g.setFont(letterFont);
		
		// Center the letter
		FontMetrics metrics = g.getFontMetrics(letterFont);
		int w = metrics.charWidth(beadUsage.getLetter());
		int a = metrics.getAscent();
		int d = metrics.getDescent();
		
		g.drawString(
				""+beadUsage.getLetter(), 
				(drawX + (BEAD_WIDTH-w)/2),
				drawY + (a + (BEAD_HEIGHT - (a + d)) / 2));
	}

	private static int clamp(int val, int min, int max) {
		return (val < min) ? min : val > max ? max : val;
	}

	public void setZoomFactor(float zoom){
		this.zoom = zoom;
		recalculatePanelSize();
		repaint();
	}
	
	public float getZoomFactor() {
		return zoom;
	}
	
	/**
	 * Recalculate panel size based on zoom and stitch size
	 */
	public void recalculatePanelSize(){		
		setPreferredSize(new Dimension(
				(int) ((STITCH_X + SIZE_EPS + stitchPattern.getWidth()*BEAD_WIDTH) * zoom), 
				(int) ((STITCH_Y + SIZE_EPS + stitchPattern.getHeight()*BEAD_HEIGHT) * zoom)));
		revalidate();
	}
	
	public int getStitchWidth(){
		return (int) ((STITCH_X + SIZE_EPS + stitchPattern.getWidth()*BEAD_WIDTH) * zoom);
	}
	
	public int getStitchHeight(){
		return (int) ((STITCH_Y + SIZE_EPS + stitchPattern.getHeight()*BEAD_HEIGHT) * zoom);
	}
	
	@Override
	public void mouseWheelMoved(MouseWheelEvent e) {
		float newZoom = zoom + e.getWheelRotation();
		if( newZoom < 1.0f ) newZoom = 1.0f;
		if( newZoom > 10.0f ) newZoom = 10.0f;
		
		setZoomFactor(newZoom);
	}
	
	public int xToBeadX(int x){
		x = (int) (x/zoom);
		return (x - STITCH_X) / BEAD_WIDTH;
	}
	
	public int yToBeadY(int x, int y){
		y = (int) (y/zoom);
		if( stitchPattern.getStitchType() == StitchType.PEYOTE && (xToBeadX(x)%2==1)){
			y -= BEAD_HEIGHT/2;
		}
		
		return (y - STITCH_Y) / BEAD_HEIGHT;
	}

	public StitchPattern getStitchPattern() {
		return stitchPattern;
	}

	public void setStitchPattern(StitchPattern stitchPattern) {
		this.stitchPattern = stitchPattern;
	}

	/**
	 * @return the showFrame
	 */
	public boolean isShowFrame() {
		return showFrame;
	}

	/**
	 * @param showFrame the showFrame to set
	 */
	public void setShowFrame(boolean showFrame) {
		this.showFrame = showFrame;
	}
}
